How to create an additional type of Scheduled Operation
EMu provides three Scheduled Operations functions by default:
This section examines how to create an additional type of Scheduled Operation:
Each type of Scheduled Operation (e.g. Delete, Merge, etc.) is defined by a script that resides under the etc/operations
or local/etc/operations directory on the EMu server.
Note:
When adding a script for an additional type of Scheduled Operation for your EMu system, place it under local/etc/operations
to avoid the risk of having it overwritten during EMu upgrades.
The script includes the name of the operation that will be listed in the Type: (Operation) drop list on the Operation tab of the Scheduled Operations module.
When the emuoperations
process runs it scans the etc/operations and local/etc/operations directories to locate the scripts for all types of Scheduled Operations (files that end with a .pl extension) and registers a name for each type of operation found. The following example registers the Delete Scheduled Operation:
sub
Register
{
my $plugins = shift;
#
# We handle the "Delete" method.
#
$plugins->{"Delete"} = \&Delete;
}
When a new type of Scheduled Operation is added to EMu, a Lookup List entry needs to be added to the Operation Type Lookup List. For the above example a Lookup List record was added to the Operation Type Lookup List with a value of Delete
:
When scheduling an operation in a record in the Scheduled Operations module, the operation can be scheduled to commence:
- At A Specified Time
-OR-
- Immediately
If Commence: (Execution) is set to Immediately, the operation will be invoked as soon the Scheduled Operations record is saved. The operation will commence running on the EMu server and control returned to the user to continue with their work.
If Commence: (Execution) is set to At A Specified Time, the operation will be invoked by the emuoperations
script on the EMu server at the appropriate time.
Note: The execution of each pending operation consumes a licence in the same way that a user would consume a licence to complete the task. Similarly to users performing tasks, multiple operations can be run simultaneously up to the system licence limit.
The emuoperations
script is designed to be run from cron with an entry similar to the following:
30 17 * * * /home/ke/emu/client/bin/emurun emuoperations 2>&1 | /home/ke/emu/client/bin/emurun emulogger -t "KE EMu Operations" -z operations
The script is typically run once per day but can be configured to run any number of times during the day. When the emuoperations
script runs it looks for any operations that were scheduled to run prior to the current date and time and commences them.
Note:
A date and time specified in a Scheduled Operations record is thus the earliest that the operation will be run. The actual time at which an operation is run will depend on when the emuoperations
script is scheduled to run: emuoperations is the script used to execute an operation that has been scheduled in a record in the Schedule Operations module. When emuoperations is run, it looks for any operations that were scheduled to run prior to the current date and time and commences them. Thus, if emuoperations is scheduled to run once per day, it will commence any operation scheduled to run in the previous 24 hours: in theory an operation could have been scheduled to run 23 hours and 59 minutes earlier. If emuoperations is to be run once per day, it probably makes sense therefore to schedule operations close to the time at which emuoperations is run. Alternatively, emuoperations can be run at various times throughout the day.
emuoperations
will also re-run any previous operations that did not complete.
Each type of Scheduled Operation registers a function that is called to process the operation. For example, the Delete Scheduled Operation is performed by a registered function called Delete
:
sub
Register
{
my $plugins = shift;
#
# We handle the "Delete" method.
#
$plugins->{"Delete"} = \&Delete;
}
The function is passed two parameters:
- An IMu session that allows access to EMu records to perform the operation.
- A hash of data from a Scheduled Operations record with details about this particular operation (i.e. when, what records are affected, what module, etc.).
sub
Delete
{
my $imusession = shift;
my $record = shift;
#
# Run the "Delete" operation.
#
...
}
The list of keys available in the hash are:
|
The IRN of a record in the Scheduled Operations module with details about this scheduled operation. |
|
The name of the operation. |
|
The type of operation. |
|
The module the operation is to be performed on. |
|
The target IRN for the Merge operation. |
|
The list of IRNs that the operation needs to process. |
|
The list of IRNs that the operation has already processed. Note: Typically this would be an empty list except when an operation failed to complete. |
|
The directory that contains files / information required by an operation to process. |
|
An identifier to add to records updated as part of running the operation. |
The values for the keys are accessed through the $record
parameter, e.g.:
$record->{Module}
-OR-
@{$record->{IrnsToProcess}}
In this example a list of IRNs is deleted:
#!/usr/bin/perl
use strict;
use warnings;
use lib "$ENV{EMUPATH}/utils/imu/lib";
use IMu::Module;
#
# Registration function.
#
no warnings 'redefine';
sub
Register
{
my $plugins = shift;
#
# We handle the "Delete" method.
#
$plugins->{"Delete"} = \&Delete;
}
use warnings 'redefine';
#
# The handler for the "Delete" operation
#
sub
Delete
{
my ($imusession, $record) = @_;
my ($attachments, $start, @deleteirns, $irn, $i);
#
# Check that we have the required information
#
if (! defined($record->{IrnsToProcess}) || @{$record->{IrnsToProcess}} == 0)
{
FileLog("Error: no irns supplied for deletion");
return(1);
}
elsif (! defined($record->{Module}) or $record->{Module} eq "")
{
FileLog("Error: delete module is not defined");
return(1);
}
#
# Get the other information that we need to process
#
$attachments = GetAttachmentFields($record->{Module});
@deleteirns = @{$record->{IrnsToProcess}};
$start = GetStartPosition($record);
FileLog("Running DELETE plugin for $record->{Module}");
FileLog("%d records scheduled for deletion, starting at position $start", scalar(@deleteirns));
#
# Now delete each record in turn
#
for ($i = $start; $i < @deleteirns; $i++)
{
$irn = $deleteirns[$i];
FileLog("Deleting irn $irn...");
last if (! ProcessDeletion($imusession, $attachments, $irn, $record));
AddToProcessed($irn);
}
return($i != @deleteirns);
}
#
# Do the actual deletion work
#
sub
ProcessDeletion
{
my ($imusession, $attachments, $irn, $record) = @_;
my ($table, $colname, $module, @matches, $hits, %found, $key, $column);
eval
{
%found = ();
foreach $key (keys %{$attachments})
{
#
# The assignment here is unusual but it gets around an
# odd foreach scoping problem after an exception is thrown.
#
$table = $key;
$module = IMu::Module->new($table, $imusession);
foreach $column (keys %{$attachments->{$table}})
{
#
# Find records that match this irn
#
$colname = $column;
$hits = $module->findTerms([$colname, $irn]);
next if ($hits <= 0);
#
# Add records to found hash
#
FileLog("Found $hits matches for $colname in $table");
push(@{$found{$table}->{$colname}}, GetMatches($module));
}
}
};
if ($@)
{
FileLog("Error: failed to process $colname in $table for irn $irn: $@");
return(0);
}
@matches = keys %found;
if (@matches)
{
#
# Log that we cannot delete the record
#
FileLog("Unable to delete irn $irn because it is attached in the following places:");
foreach $table (@matches)
{
foreach $colname (keys %{$found{$table}})
{
FileLog("\tModule: $table, Column: $colname, Record(s): " . join(", ", @{$found{$table}->{$colname}}));
}
}
}
else
{
#
# Delete the record
#
DeleteRecord($imusession, $irn, $record);
}
#
# Add irn to processed
#
return(1);
}
#
# Delete the record
#
sub
DeleteRecord
{
my ($imusession, $irn, $record) = @_;
my ($module, $hits, $result);
eval
{
$module = IMu::Module->new($record->{Module}, $imusession);
$hits = $module->findKey($irn);
if ($hits > 0)
{
$result = $module->remove("start", 0, 1);
if ($result == 0)
{
FileLog("Failed to delete irn $irn from $record->{Module}");
}
}
else
{
FileLog("Failed to find irn $irn in $record->{Module}");
}
};
if ($@)
{
FileLog("Failed to delete $irn from $record->{Module}: $@");
}
}
#
# Get all the records that match the attachment query
#
sub
GetMatches
{
my ($module) = @_;
my ($result, @matches, $row);
#
# Get all of the records at once
#
@matches = ();
$result = $module->fetch("start", 0, -1, "irn");
if ($result->{count})
{
#
# Get the irn for each row and push it to the list of matches
#
foreach $row (@{$result->{rows}})
{
push(@matches, $row->{irn});
}
}
return(@matches);
}
1;